﻿using System;
using System.IO;
using System.Xml;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.Windows.Controls;
using MJB.UI.ContentEditor;
using MJB.UI.ContentEditor.Windows;
using MJB.UI.ContentEditor.Document;


namespace MJB.UI.ContentEditor.Commands
{
    /// <summary>
    /// This class defines commands that are implemented by the context menu 
    /// of the tree nodes in the file tree.
    /// </summary>
    public class TreeNodeContextMenuCommands
    {
        #region Commands

        /// <summary>
        /// This command is fired when the user wants to create a new folder in the repo.
        /// </summary>
        public static readonly RoutedUICommand NewFolderCommand = new RoutedUICommand("New Folder",
            "NewFolder",
            typeof(TreeNodeContextMenuCommands),
            new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.F, ModifierKeys.Control) }));

        /// <summary>
        /// This command is fired when the user wants to create a new document in the repo.
        /// </summary>
        public static readonly RoutedUICommand NewDocumentCommand = new RoutedUICommand("New Document",
            "NewDocument",
            typeof(TreeNodeContextMenuCommands),
            new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.N, ModifierKeys.Control) }));

        /// <summary>
        /// This command is fired when the user wants to delete a file or folder in the repo.
        /// </summary>
        public static readonly RoutedUICommand DeleteCommand = new RoutedUICommand("Delete",
            "Delete",
            typeof(TreeNodeContextMenuCommands),
            new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.Delete) }));

        /// <summary>
        /// This command is fired when the user wants to backup a file or folder to disk.
        /// </summary>
        public static readonly RoutedUICommand BackupToDiskCommand = new RoutedUICommand("Backup To Disk",
            "BackupToDisk",
            typeof(TreeNodeContextMenuCommands),
            new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.B, ModifierKeys.Control) }));

        /// <summary>
        /// This command is fired when the user wants to restore a file or folder to disk.
        /// </summary>
        public static readonly RoutedUICommand RestoreFromDiskCommand = new RoutedUICommand("Restore From Disk",
            "RestoreFromDisk",
            typeof(TreeNodeContextMenuCommands),
            new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.R, ModifierKeys.Control) }));

        #endregion
        #region Constructor

        /// <summary>
        /// The static constructor will assign event handlers for the context menu commands.
        /// </summary>
        static TreeNodeContextMenuCommands()
        {
            CommandManager.RegisterClassCommandBinding(typeof(TreeView),
                new CommandBinding(TreeNodeContextMenuCommands.NewFolderCommand,
                    new ExecutedRoutedEventHandler(TreeNodeContextMenuCommands.OnNewFolderCommand)));

            CommandManager.RegisterClassCommandBinding(typeof(TreeView),
                new CommandBinding(TreeNodeContextMenuCommands.NewDocumentCommand,
                    new ExecutedRoutedEventHandler(TreeNodeContextMenuCommands.OnNewDocumentCommand)));

            CommandManager.RegisterClassCommandBinding(typeof(TreeView),
                new CommandBinding(TreeNodeContextMenuCommands.DeleteCommand,
                    new ExecutedRoutedEventHandler(TreeNodeContextMenuCommands.OnDeleteCommand)));

            CommandManager.RegisterClassCommandBinding(typeof(TreeView),
                new CommandBinding(TreeNodeContextMenuCommands.BackupToDiskCommand,
                    new ExecutedRoutedEventHandler(TreeNodeContextMenuCommands.OnBackupToDiskCommand)));

            CommandManager.RegisterClassCommandBinding(typeof(TreeView),
                new CommandBinding(TreeNodeContextMenuCommands.RestoreFromDiskCommand,
                    new ExecutedRoutedEventHandler(TreeNodeContextMenuCommands.OnRestoreFromDiskCommand)));

        }

        #endregion
        #region Event Handlers

        /// <summary>
        /// Called when the new folder command is executed.
        /// </summary>
        /// <param name="sender">The object generating the event.</param>
        /// <param name="args">The arguments of the event.</param>
        public static void OnNewFolderCommand(object sender, ExecutedRoutedEventArgs args)
        {
            FrameworkElement frameworkElement = sender as FrameworkElement;
            if(frameworkElement == null)
                return;

            FileNode fileNode = frameworkElement.DataContext as FileNode;
            if(fileNode == null)
                return;

            NewFolderWindow window = new NewFolderWindow();
            window.Owner = Application.Current.MainWindow;
            window.ShowDialog();

            if(window.NewFolderName != null)
            {
                try
                {
                    StringBuilder pathBuilder = new StringBuilder();
                    pathBuilder.Append(fileNode.Path);
                    pathBuilder.Append("/");
                    pathBuilder.Append(window.NewFolderName);

                    string path = pathBuilder.ToString();

                    XmlManagementService managementService = new XmlManagementService();
                    managementService.CreateDirectory(path);

                    FileNode newNode = new FileNode();
                    newNode.Name = window.NewFolderName;
                    newNode.Path = path;
                    newNode.IsDirectory = true;
                    fileNode.ChildNodes.Add(newNode);
                    newNode.ParentNode = fileNode;

                }
                catch(Exception e)
                {
                    e.DisplayError("There was an error creating the new directory on the remote server.");
                }

            }

        }

        /// <summary>
        /// Called when the new document command is executed.
        /// </summary>
        /// <param name="sender">The object generating the event.</param>
        /// <param name="args">The arguments of the event.</param>
        public static void OnNewDocumentCommand(object sender, ExecutedRoutedEventArgs args)
        {
            FrameworkElement frameworkElement = sender as FrameworkElement;
            if(frameworkElement == null)
                return;

            FileNode fileNode = frameworkElement.DataContext as FileNode;
            if(fileNode == null)
                return;

            NewDocumentWindow window = new NewDocumentWindow();
            window.Owner = Application.Current.MainWindow;
            window.ShowDialog();

            if(window.NewDocumentName != null)
            {
                try
                {
                    // Create a blank document to send to the server:
                    IDocumentReader reader = DocumentReaderFactory.GetLatestReader();
                    ISection section = reader.CreateNewSection();
                    section.SectionTitle = "Untitled";
                    reader.Sections.Add(section);

                    StringBuilder pathBuilder = new StringBuilder();
                    pathBuilder.Append(fileNode.Path);
                    pathBuilder.Append("/");
                    pathBuilder.Append(window.NewDocumentName);
                    pathBuilder.Append(".xml");
                    string path = pathBuilder.ToString();

                    XmlManagementService managementService = new XmlManagementService();
                    managementService.CreateFile(path);
                    managementService.UpdateFile(path, reader);

                    FileNode newNode = new FileNode();
                    newNode.Name = window.NewDocumentName;
                    newNode.Path = path;
                    newNode.IsDirectory = false;
                    fileNode.ChildNodes.Add(newNode);
                    newNode.ParentNode = fileNode;

                }
                catch(Exception e)
                {
                    e.DisplayError("There was an error creating the new directory on the remote server.");
                }

            }

        }

        /// <summary>
        /// Called when the delete command is executed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        public static void OnDeleteCommand(object sender, ExecutedRoutedEventArgs args)
        {
            FrameworkElement frameworkElement = sender as FrameworkElement;
            if(frameworkElement == null)
                return;

            FileNode fileNode = frameworkElement.DataContext as FileNode;
            if(fileNode == null)
                return;

            StringBuilder messageBuilder = new StringBuilder();
            if(fileNode.IsDirectory)
            {
                messageBuilder.Append("'");
                messageBuilder.Append(fileNode.Path);
                messageBuilder.Append("' is a directory.");
                messageBuilder.Append(Environment.NewLine);
                messageBuilder.Append("Are you sure you want to delete it and all of its contents?");
            }
            else
            {
                messageBuilder.Append("Are you sure you want to delete the file:");
                messageBuilder.Append(Environment.NewLine);
                messageBuilder.Append("'");
                messageBuilder.Append(fileNode.Path);
                messageBuilder.Append("'?");
            }

            if(messageBuilder.DisplayQuestionDialog())
            {
                try
                {

                    XmlManagementService managementService = new XmlManagementService();

                    if(fileNode.IsDirectory)
                    {
                        managementService.DeleteDirectory(fileNode.Path);
                    }
                    else
                    {
                        managementService.DeleteFile(fileNode.Path);
                    }

                    if(fileNode.ParentNode != null)
                    {
                        FileNode parentNode = fileNode.ParentNode;
                        parentNode.ChildNodes.Remove(fileNode);
                        fileNode.ParentNode = null;

                        // Add a place holder if this is the last node:
                        if(parentNode.ChildNodes.Count == 0)
                        {
                            FileNode placeHolder = new FileNode();
                            placeHolder.Path = parentNode.Path;
                            placeHolder.Name = "...";
                            placeHolder.ParentNode = parentNode;
                            parentNode.ChildNodes.Add(placeHolder);
                        }

                    }

                }
                catch(Exception e)
                {
                    e.DisplayError(new StringBuilder("There was an error deleting the")
                                             .Append((fileNode.IsDirectory) ? " directory " : " file ")
                                             .Append("'")
                                             .Append(fileNode.Path)
                                             .Append("'"));

                }

            }

        }

        /// <summary>
        /// Called when the backup to disk command is executed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        public static void OnBackupToDiskCommand(object sender, ExecutedRoutedEventArgs args)
        {
            FrameworkElement frameworkElement = sender as FrameworkElement;
            if(frameworkElement == null)
                return;

            FileNode fileNode = frameworkElement.DataContext as FileNode;
            if(fileNode == null)
                return;

            MainWindow mainWindow = (MainWindow)Application.Current.MainWindow;

            Cursor originalCursor = mainWindow.Cursor;
            mainWindow.Cursor = Cursors.Wait;

            try
            {
                XmlManagementService managementService = new XmlManagementService();
                string directoryPath = mainWindow.GetPhysicalPathForFileNode(fileNode);

                if(!System.IO.Path.HasExtension(directoryPath))
                {
                    if(!Directory.Exists(directoryPath))
                        Directory.CreateDirectory(directoryPath);
                }

                // We will use a stack to fill in the rest of the files and directories:
                Stack<FileNode> childNodeStack = new Stack<FileNode>();
                childNodeStack.Push(fileNode);

                while(childNodeStack.Count > 0)
                {
                    FileNode currentChildNode = childNodeStack.Pop();
                    if(currentChildNode.ChildNodes.Count == 1)
                    {
                        if(currentChildNode.ChildNodes[0].Name == "...")
                        {
                            // Fill in the rest of the nodes here:
                            FileNode childNodes = mainWindow.GenerateNode(currentChildNode.Path);
                            currentChildNode.ChildNodes.Clear();

                            // Port all of the nodes from the returned node 
                            // over to the existing tree view node:
                            foreach(FileNode childNodeChildNode in childNodes.ChildNodes)
                            {
                                childNodeChildNode.ParentNode = currentChildNode;
                                currentChildNode.ChildNodes.Add(childNodeChildNode);
                            }

                        }

                    }

                    // Now, add all directories:
                    var query = from childNodeChildNode in currentChildNode.ChildNodes
                                where childNodeChildNode.IsDirectory
                                select childNodeChildNode;

                    query.ForEach(c => childNodeStack.Push(c));

                    // Now see if the directory exists:
                    string childDirectoryPath = mainWindow.GetPhysicalPathForFileNode(currentChildNode);
                    if(!System.IO.Path.HasExtension(directoryPath))
                    {
                        if(!Directory.Exists(childDirectoryPath))
                            Directory.CreateDirectory(childDirectoryPath);
                    }

                }


                // Now, finally, we will suck down all of the content and back it up locally!
                childNodeStack.Push(fileNode);
                while(childNodeStack.Count > 0)
                {
                    // Add all child nodes into the stack:
                    FileNode childNode = childNodeStack.Pop();
                    childNode.ChildNodes.ForEach(c => childNodeStack.Push(c));

                    // If this is a file, suck down the file:
                    if(!childNode.IsDirectory)
                    {
                        IDocumentReader reader = managementService.GetFile(childNode.Path);

                        // Write to a file and save it locallay:
                        string filePath = mainWindow.GetPhysicalPathForFileNode(childNode);
                        using(XmlTextWriter textWriter = new XmlTextWriter(File.Open(filePath, FileMode.OpenOrCreate), Encoding.UTF8))
                            reader.WriteXml(textWriter);

                    }

                }


            }
            catch(Exception e)
            {
                e.DisplayError(new StringBuilder("There was an error backing up the path '")
                                         .Append(fileNode.Path)
                                         .Append("' to local disc."));
            }
            finally
            {
                mainWindow.Cursor = originalCursor;
            }

        }

        /// <summary>
        /// Called when the restore from disk command is executed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        public static void OnRestoreFromDiskCommand(object sender, ExecutedRoutedEventArgs args)
        {
            FrameworkElement frameworkElement = sender as FrameworkElement;
            if(frameworkElement == null)
                return;

            FileNode fileNode = frameworkElement.DataContext as FileNode;
            if(fileNode == null)
                return;

            MainWindow mainWindow = (MainWindow)Application.Current.MainWindow;

            Cursor originalCursor = mainWindow.Cursor;
            mainWindow.Cursor = Cursors.Wait;

            try
            {

                string path = mainWindow.GetPhysicalPathForFileNode(fileNode);
                XmlManagementService managementService = new XmlManagementService();

                // Handle directories:
                if(fileNode.IsDirectory)
                {
                    // Push all sub directories onto the stack:
                    Stack<string> directoryStack = new Stack<string>();
                    directoryStack.Push(path);

                    // Make sure all the directories exist on the server:
                    while(directoryStack.Count > 0)
                    {
                        string currentDirectory = directoryStack.Pop();
                        string serverPath = mainWindow.GetServerPathForPhysicalPath(currentDirectory);

                        try
                        {
                            managementService.CreateDirectory(serverPath);
                        }
                        catch
                        {
                            // This is ignored.  We get an exception
                            // if the directory aleady exists.
                        }

                        // Also, we can now create the files in folder:
                        string[] files = Directory.GetFiles(currentDirectory);
                        foreach(string file in files)
                        {
                            string serverFilePath = mainWindow.GetServerPathForPhysicalPath(file);

                            try
                            {
                                managementService.CreateFile(serverFilePath);
                            }
                            catch
                            {
                                // Again, ignore this.  If the file already exists, that's fine with us!
                            }

                            // Now, we can update the file:
                            mainWindow.SendFileToServer(serverFilePath, file);

                            // Lastly, if this file is currently being displayed, update it:
                            mainWindow.ReloadOpenDocument(serverFilePath);

                        }

                        // Now, push all the other directories onto the stack:
                        string[] childDirectories = Directory.GetDirectories(currentDirectory);
                        childDirectories.ForEach(d => directoryStack.Push(d));

                    }

                    // Lastly, we need to update the tree.  To do this, we need to regenerate the 
                    // FileNode that was just clicked!
                    FileNode parentNode = fileNode.ParentNode;
                    parentNode.ChildNodes.Remove(fileNode);

                    FileNode newFileNode = mainWindow.GenerateNode(fileNode.Path);
                    parentNode.ChildNodes.Add(newFileNode);
                    newFileNode.ParentNode = parentNode;

                }
                else
                {
                    // Much easier: upload a file:
                    try
                    {
                        mainWindow.SendFileToServer(fileNode.Path, path);
                        mainWindow.ReloadOpenDocument(fileNode.Path);
                    }
                    catch(Exception e)
                    {
                        e.DisplayError("There was an error restoring the file from disk.");
                    }

                }

            }
            finally
            {
                mainWindow.Cursor = originalCursor;
            }

        }

        #endregion

    }

}
